package main;

import individuals.Individual;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import mutation.ClassicMutation;
import mutation.MutationTypes;
import algorythms.Elite;
import algorythms.MixtoRuleta;
import algorythms.MixtoUniversal;
import algorythms.Ruleta;
import algorythms.SelectionMethods;
import algorythms.TerminationCond;
import algorythms.Torneos;
import algorythms.Universal;
import cross.ClassicCross;
import cross.Cross;
import cross.CrossTypes;
import cross.MultipleCross;
import cross.RingCross;
import cross.SegmentedCross;

public class GeneticEngine {

	private static final int NUM_ARGS = 11;
	private static boolean searchMax = true;
	private static final int RANGE_MIN = -6;
	private static final int RANGE_MAX = 6;
	private static int killed = 0;
	private static int generations = 0;
	/*Par�metros para la mutaci�n no uniforme*/
	/*Cantidad de generaciones en las cuales se eval�a cambiar de Pmut*/
	private static int nGenerations = 7;
	private static double c = 0.1;
	private static double mutationProb = 0;
	private static double min_progress = 0.00001;
	private static double oldBest = 0;
	private static double newBest = 0;
	private static double structurePercentage = 0.05;
	//la cantidad de generaciones que dejo pasar antes de chequear por contenido
	private static int contentCheckGens = 20;
	private static int fighters = 4;
	private static int crossPoints = 6;
	private static double segCrossPercentage = 0.2;
	
	public static void main(String[] args) {
		
		if(args.length != NUM_ARGS){
			throw new RuntimeException("Error in parameters");
		}
		
		int popSize = 0;
		
		int maxGens = 0;
		int numReprodInd = 0;
		int numIndMix = 0;
		
		double error = 0.0001;
		CrossTypes cross = null;
		MutationTypes mutation = null;
		SelectionMethods selection = null;
		TerminationCond termination = null;
		SelectionMethods replace = null;
		
		int i = 0;
		for (i = 0; i < args.length; i++) {
			switch (i) {
			case 0:
				//read popSize
				popSize = Integer.parseInt(args[0]);
				break;
			case 1:
				//read numOfGenerations
				maxGens = Integer.parseInt(args[1]);
				break;
			case 2:
				//read crossType
				if(args[2].equalsIgnoreCase("CL")){
					cross = CrossTypes.CLASICO;
				}else if(args[2].equalsIgnoreCase("MU")){
					cross = CrossTypes.MULTIPLE;
				}else if(args[2].equalsIgnoreCase("SE")){
					cross = CrossTypes.SEGMENTADO;
				}else if(args[2].equalsIgnoreCase("AN")){
					cross = CrossTypes.ANULAR;
				}
				break;
			case 3:
				//read mutationType
				if(args[3].equalsIgnoreCase("CL")){
					mutation = MutationTypes.CLASICO;
				}else if(args[3].equalsIgnoreCase("NO")){
					mutation = MutationTypes.NO_UNIFORME;
				}
				break;
			case 4:
				//read selectionMethods
				if(args[4].equalsIgnoreCase("EL")){
					selection = SelectionMethods.ELITE;
				}else if(args[4].equalsIgnoreCase("RU")){
					selection = SelectionMethods.RULETA;
				}else if(args[4].equalsIgnoreCase("UN")){
					selection = SelectionMethods.UNIVERSAL;
				}else if(args[4].equalsIgnoreCase("TO")){
					selection = SelectionMethods.TORNEOS;
				}else if(args[4].equalsIgnoreCase("MR")){
					selection = SelectionMethods.MIXTO_R;
				}else if(args[4].equalsIgnoreCase("MU")){
					selection = SelectionMethods.MIXTO_U;
				}
				break;
			case 5:
				//read terminationCond
				if(args[5].equalsIgnoreCase("CO")){
					termination = TerminationCond.CONTENT;
				}else if(args[5].equalsIgnoreCase("ER")){
					termination = TerminationCond.ERROR;
				}else if(args[5].equalsIgnoreCase("MA")){
					termination = TerminationCond.MAX_GEN;
				}else if(args[5].equalsIgnoreCase("ST")){
					termination = TerminationCond.STRUCTURE;
				}
				break;
			case 6:
				//read selectionMethods for replace
				if(args[6].equalsIgnoreCase("EL")){
					replace = SelectionMethods.ELITE;
				}else if(args[6].equalsIgnoreCase("RU")){
					replace = SelectionMethods.RULETA;
				}else if(args[6].equalsIgnoreCase("UN")){
					replace = SelectionMethods.UNIVERSAL;
				}else if(args[6].equalsIgnoreCase("TO")){
					replace = SelectionMethods.TORNEOS;
				}else if(args[6].equalsIgnoreCase("MR")){
					replace = SelectionMethods.MIXTO_R;
				}else if(args[6].equalsIgnoreCase("MU")){
					replace = SelectionMethods.MIXTO_U;
				}
				break;
			case 7:
				//read numReprodInd
				numReprodInd = Integer.parseInt(args[7]);
				break;
			case 8:
				//read mutationProb
				mutationProb = Double.parseDouble(args[8]);
				break;
			case 9:
				//read numIndMix
				numIndMix = Integer.parseInt(args[9]);
				break;
			case 10:
				//read searchMax
				if(args[10].equalsIgnoreCase("MAX")){
					searchMax = true;
				}else if(args[10].equalsIgnoreCase("MIN")){
					searchMax = false;
				}

				break;

			default:
				break;
			}
		}
		
		System.out.println(popSize);
		System.out.println(maxGens);
		System.out.println(cross.name());
		System.out.println(mutation.name());
		System.out.println(selection.name());
		System.out.println(termination.name());
		System.out.println(replace.name());
		System.out.println(numReprodInd);
		System.out.println(mutationProb);
		System.out.println(numIndMix);
		System.out.println(searchMax);
		
		List<Individual> pop = null;
		List<Individual> reprodPop = new ArrayList<Individual>();
		
		Function4.setSign((searchMax?1:-1));
		
		Function4 func = new Function4();
		Individual.setFunc(func);
		
		Random randGen = new Random(127);
		Individual.setRand(randGen);
		
		//inicializar la poblacion
		pop = init(popSize,func);
		
		if(pop == null){
			throw new RuntimeException("No se pudo inicializar la poblacion");
		}
		
		newBest = getNewBest(pop).getAptitud();
		
		while (!checkCond(pop,termination,generations,maxGens,error)) {
			System.out.println(generations);
			//seleccionar individuos para reproduccion
			reprodPop = selectIndividuals(pop,selection,numReprodInd,numIndMix);
			
			//cruce y mutacion
			pop.addAll(reproduce(pop,reprodPop,cross,mutation));
			
			//reemplazo de poblacion
			pop = selectIndividuals(pop,replace,popSize,numIndMix);
			
			newBest = getNewBest(pop).getAptitud();
			
			generations++;
		}
		
		printStatics(pop,generations);
		
	}

	private static Individual getNewBest(List<Individual> pop) {
		Individual best = pop.get(0);
		
		for (Individual ind : pop) {
			best = (best.getAptitud() > ind.getAptitud())?best:ind;
		}
		
		return best;
	}

	private static void printStatics(List<Individual> pop, int generations) {
		
		for (int i = 0; i < pop.size(); i++) {
			System.out.println("Ind: "+i+": "+pop.get(i).getGen());
		}
		
		System.out.println("generations: "+generations);
		System.out.println("Killed: "+killed);
		System.out.println("Best: "+getNewBest(pop).getGen());
	}

	private static boolean checkCond(List<Individual> pop,
			TerminationCond cond, int generations, int maxGens, double error) {
		
		switch (cond) {
		case CONTENT:
			
			if(generations % contentCheckGens != 0){
				return false;
			}
			
			double progress = newBest - oldBest;
			
			oldBest = newBest;
			
			if(progress < min_progress){
				return true;
			}
			
			break;
		case ERROR:
			double compare = Individual.getFunc().maxima();
			if(!searchMax){
				compare = Individual.getFunc().minima();
			}
			
			for (Individual ind : pop) {
				if(Math.abs(ind.getGen()-compare) < error){
					return true;
				}
			}
			break;
		case MAX_GEN:
			return(generations >= maxGens);
		case STRUCTURE:
			
			double gen = truncate(pop.get(0).getGen());
			double comp = 0;
			int counter = 0;
			int best = 0;
			
			for (Individual ind : pop) {
				comp = truncate(ind.getGen());
				if(gen == comp){
					counter++;
				}else{
					best = (best > counter)?best:counter;
					counter = 0;
				}
				gen = comp;
			}
			System.out.println("best: "+best);
			if(best >= (pop.size() * structurePercentage)){
				return true;
			}
			
			break;
		default:
			break;
		}
		
		return false;
	}
	
	private static double truncate(double x)
	{
		//trunco a dos decimales
	    long y=(long)(x*100);
	    return (double)y/100;
	}

	private static List<Individual> reproduce(List<Individual> pop,List<Individual> reprodPop, CrossTypes cross, MutationTypes mutation) {
		ArrayList<Individual> children = new ArrayList<Individual>();
		Cross cro = null;
		
		switch (cross) {
		case CLASICO:
			cro = new ClassicCross();
			break;
		case MULTIPLE:
			cro = new MultipleCross(crossPoints);
			break;
		case SEGMENTADO:
			cro = new SegmentedCross(segCrossPercentage);
		break;
		case ANULAR:
			cro = new RingCross();
		break;
		default:
			break;
		}
		
		//TODO preguntar si esto es asi
		for (int i = 0; i < reprodPop.size()-1; i++) {
			children.addAll(cro.Apply(reprodPop.get(i), reprodPop.get(i+1)));
		}
		
		switch (mutation) {
		case CLASICO:
			ClassicMutation mut = new ClassicMutation(mutationProb);
			
			for (int i = 0; i < children.size(); i++) {
				mut.mutate(children.get(i));
			}
			break;
		case NO_UNIFORME:
			if(generations%nGenerations == 0){
				if(evaluatePopulation(pop))
					mutationProb = mutationProb * c;
			}
			ClassicMutation notUM = new ClassicMutation(mutationProb);
			for (int i = 0; i < children.size(); i++) {
				notUM.mutate(children.get(i));
			}
				
			break;
			
		default:
			break;
		}
		
		//limpio los NaN y los fuera de rango
		ArrayList<Individual> toRemove = new ArrayList<Individual>();
		
		for (Individual ind : children) {
			if(ind.getGen() > RANGE_MAX || ind.getGen() < RANGE_MIN 
					|| Double.isNaN(ind.getGen())){
				toRemove.add(ind);
				System.out.println("ind:"+ind.getGen()+"eliminado");
				killed++;
			}
			
		}
		
		children.removeAll(toRemove);
		
		return children;
	}

	private static List<Individual> selectIndividuals(
			List<Individual> pop, SelectionMethods method, int numReprodInd, int numIndMix) {
		
		switch (method) {
		case ELITE:
			return new Elite().applyAlgorythm(pop, numReprodInd);
		case RULETA:
			return new Ruleta().applyAlgorythm(pop, numReprodInd);
		case UNIVERSAL:
			return new Universal().applyAlgorythm(pop, numReprodInd);
		case TORNEOS:
			return new Torneos(fighters).applyAlgorythm(pop, numReprodInd);
		case MIXTO_R:
			return new MixtoRuleta().applyAlgorythm(pop, numReprodInd,numIndMix);
		case MIXTO_U:
			return new MixtoUniversal().applyAlgorythm(pop, numReprodInd,numIndMix);
		default:
			break;
		}
		return null;
	}

	private static ArrayList<Individual> init(int popSize,Function func) {
		ArrayList<Individual> ret = new ArrayList<Individual>();
		
		for (int i = 0; i < popSize; i++) {
			ret.add(new Individual());
		}
		
		return ret;
	}

	private static boolean evaluatePopulation(List<Individual> pop){
		
		Collections.shuffle(pop);
		int cant = Math.round((0.2f * pop.size()));
		double acum = 0;
		
		for(int i=0; i<cant; i++)
			acum += pop.get(i).getAptitud();
			
		/*aptitud promedio de la muestra*/
		acum /= cant;
		
		if(searchMax){
			double max = new Function4().maxima();
			if((acum < (max*0.2)) && (acum > (max *(-0.2))))
				return true;
		}else{
			double min = new Function4().minima();
			if((acum < (min*0.2)) && (acum > (min *(-0.2))))
				return true;
			
		}
		
		return false;
	}
	
}
